package com.thd.springboottest.camunda.service;

import com.thd.springboottest.camunda.dto.ProcessInstanceEntity;
import com.thd.springboottest.camunda.dto.ProcessInstanceTaskEntity;
import org.camunda.bpm.engine.*;
import org.camunda.bpm.engine.repository.Deployment;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.camunda.bpm.engine.runtime.ProcessInstanceModificationBuilder;
import org.camunda.bpm.engine.task.IdentityLink;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;

/**
 * com.thd.springboottest.camunda.service.WorkForServiceImplForCamunda
 *
 * @author: wanglei62
 * @DATE: 2022/5/29 16:03
 **/
@Service("WorkFlowServiceImplForCamunda")
public class WorkFlowServiceImplForCamunda implements WorkFlowService {
    @Autowired
    public ProcessEngine processEngine;
    @Autowired
    public RuntimeService runtimeService;
    @Autowired
    public RepositoryService repositoryService;
    @Autowired
    public TaskService taskService;
    @Autowired
    public ManagementService managementService;
    @Autowired
    public IdentityService identityService;
    @Autowired
    public HistoryService historyService;
    @Autowired
    public FormService formService;
    public Logger log = LoggerFactory.getLogger(WorkFlowServiceImplForCamunda.class);
    @Override
    public String  deploy(String bpmnPath) {
        Deployment deployment = repositoryService.createDeployment()
                .addClasspathResource(bpmnPath)
                .deploy();
        return deployment.getId();
    }

    @Override
    public String deploy(InputStream is,String bpmnFileName) {
        Deployment deployment = repositoryService.createDeployment()
                .addInputStream(bpmnFileName,is)
                .deploy();
        return deployment.getId();
    }
    @Override
    public String deploy(String bpmnContent ,String bpmnFileName){
        Deployment deployment = repositoryService.createDeployment()
                .addString(bpmnFileName,bpmnContent)
                .deploy();
        return deployment.getId();
    };

    @Override
    public void deleteDeploy(String deploymentId){
        repositoryService.deleteDeployment(deploymentId);
    }

    @Override
    public void deleteDeployCascade(String deploymentId) {
        System.out.println("================ deleteDeploy [" + deploymentId + "] Start ===================");
        repositoryService.deleteDeployment(deploymentId,true);
        System.out.println("================ deleteDeploy [" + deploymentId + "] End ===================");
    }

    @Override
    public void deleteProcessDefinition(String procDefId){
        this.repositoryService.deleteProcessDefinition(procDefId);
    };
    @Override
    public void deleteProcessDefinitionCascade(String procDefId){
        this.repositoryService.deleteProcessDefinition(procDefId,true);
    };
    @Override
    public ProcessInstanceEntity startProcess(String processKey, String businessKey, Map var){
        ProcessInstance pi = this.runtimeService.startProcessInstanceByKey(processKey,businessKey,var);
        ProcessInstanceEntity piEntity = new ProcessInstanceEntity();
        piEntity.setBusinessKey(pi.getBusinessKey());
        piEntity.setProcessInstanceId(pi.getProcessInstanceId());
        piEntity.setProcDefId(pi.getProcessDefinitionId());
        return piEntity;
    };

    public void cancelProcess(String businessKey,String reason){
        ProcessInstance pi = this.runtimeService.createProcessInstanceQuery().processInstanceBusinessKey(businessKey).singleResult();
        if(null == pi){
            log.error("Process Instance can not be found");
            throw new RuntimeException("Process Instance can not be found");
        }
        this.runtimeService.deleteProcessInstance(pi.getProcessInstanceId(),reason);
    };

    @Override
    public void addVariableForProcessInstance(String processInstanceId,Map var){
        this.runtimeService.setVariables(processInstanceId,var);
    };
    @Override
    public void addVariableForProcessInstanceByBusinessKey(String businessKey,Map var){
        ProcessInstance pi = this.runtimeService.createProcessInstanceQuery().processInstanceBusinessKey(businessKey).singleResult();
        this.runtimeService.setVariables(pi.getId(),var);
    };
    @Override
    public void addVariableForExecutionLocal(String executionId,Map var){
        this.runtimeService.setVariablesLocal(executionId,var);
    };
    @Override
    public void addVariableForExecution(String executionId,Map var){
        this.runtimeService.setVariables(executionId,var);
    };
    @Override
    public void addVariableForTask(String taskId,Map var){
        this.taskService.setVariables(taskId,var);
    };
    @Override
    public void addVariableForTaskLocal(String taskId,Map var){
        this.taskService.setVariablesLocal(taskId,var);
    };

    @Override
    public Map queryVariableForProcessInstance(String processInstanceId) {
        return this.runtimeService.getVariables(processInstanceId);
    }

    public Map queryVariableForProcessInstanceByBusinessKey(String businessKey){
        ProcessInstance pi = this.runtimeService.createProcessInstanceQuery().processInstanceBusinessKey(businessKey).singleResult();
        return this.queryVariableForProcessInstance(pi.getId());
    };

    @Override
    public Map queryVariableForExecution(String executionId) {
        return this.runtimeService.getVariables(executionId);
    }

    @Override
    public Map queryVariableForExecutionLocal(String executionId) {
        return this.runtimeService.getVariablesLocal(executionId);
    }

    @Override
    public Map queryVariableForTask(String taskId) {
        return this.taskService.getVariables(taskId);
    }

    @Override
    public Map queryVariableForTaskLocal(String taskId) {
        return this.taskService.getVariablesLocal(taskId);
    }

    @Override
    public List<String> queryCandidateForTask(String taskId) {
        List<IdentityLink> candidaters = taskService.getIdentityLinksForTask(taskId);
        if(null != candidaters && candidaters.size() > 0){
            return candidaters.stream().map(IdentityLink::getUserId).collect(Collectors.toList());
        }
        return new ArrayList<String>();
    }

    @Override
    public String queryAssigneeForTask(String taskId) {
        org.camunda.bpm.engine.task.Task task =  this.taskService.createTaskQuery().taskId(taskId).singleResult();
        return task.getAssignee();
    }

    @Override
    public List<ProcessInstanceTaskEntity> queryTaskForCandidate(String candidate) {
        List<org.camunda.bpm.engine.task.Task> taskList = this.taskService.createTaskQuery().taskCandidateUser(candidate).list();
        if(null!=taskList && taskList.size() > 0){
            return taskList.stream().map( item -> {
                return this.transferTask(item);
            }).collect(Collectors.toList());
        }
        return new ArrayList<ProcessInstanceTaskEntity>();
    }

    public List<ProcessInstanceTaskEntity> queryTaskForAssignee(String assignee){
        List<org.camunda.bpm.engine.task.Task> taskList = this.taskService.createTaskQuery().taskAssignee(assignee).list();
        if(null!=taskList && taskList.size() > 0) {
            return taskList.stream().map(item -> {
                return this.transferTask(item);
            }).collect(Collectors.toList());
        }
        return new ArrayList<ProcessInstanceTaskEntity>();
    };

    @Override
    public List<ProcessInstanceTaskEntity> queryTaskForUser(String userId) {
        List<ProcessInstanceTaskEntity> assigneeTaskList = this.queryTaskForAssignee(userId);
        List<ProcessInstanceTaskEntity> candidateTaskList = this.queryTaskForCandidate(userId);
        List<ProcessInstanceTaskEntity> r = new ArrayList<ProcessInstanceTaskEntity>();
        if(null != assigneeTaskList && assigneeTaskList.size() > 0){
            r.addAll(assigneeTaskList);
        }
        if(null != candidateTaskList && candidateTaskList.size() > 0){
            r.addAll(candidateTaskList);
        }
        return r;
    }

    @Override
    public boolean isCandidateOrAssigneeForTask(String taskId,String userId){
        List<ProcessInstanceTaskEntity> taskList = this.queryTaskForUser(userId);
        List<String> taskIdList = taskList.stream().map(ProcessInstanceTaskEntity::getTaskId).collect(Collectors.toList());
        return taskIdList.contains(taskId);
    };

    @Override
    public List<ProcessInstanceTaskEntity> queryTaskByProcessInstanceId(String processInstanceId) {
        List<org.camunda.bpm.engine.task.Task> taskList = this.taskService.createTaskQuery().processInstanceId(processInstanceId).list();
        if(null!=taskList && taskList.size() > 0) {
            return taskList.stream().map(item -> {
                return this.transferTask(item);
            }).collect(Collectors.toList());
        }
        return new ArrayList<ProcessInstanceTaskEntity>();
    }

    @Override
    public List<ProcessInstanceTaskEntity> queryTaskByBusinessKey(String businessKey) {
        List<org.camunda.bpm.engine.task.Task> taskList = this.taskService.createTaskQuery().processInstanceBusinessKey(businessKey).list();
        if(null!=taskList && taskList.size() > 0) {
            return taskList.stream().map(item -> {
                return this.transferTask(item);
            }).collect(Collectors.toList());
        }
        return new ArrayList<ProcessInstanceTaskEntity>();
    }

    @Override
    public ProcessInstanceTaskEntity queryTaskByTaskId(String taskId) {
        org.camunda.bpm.engine.task.Task item = this.taskService.createTaskQuery().taskId(taskId).singleResult();
        return this.transferTask(item);
    }

    @Override
    public void completeTask(String taskId, Map<String, Object> variableMap) {
        this.taskService.complete(taskId,variableMap);
    }

    @Override
    public void completeTaskWithAssignee(String assignee,String taskId, Map<String, Object> variableMap){
        this.assignAssigneeForTask(taskId,assignee);
        this.completeTask(taskId,variableMap);
    };

    @Override
    public void claimTask(String taskId, String candidate) {
        this.taskService.claim(taskId,candidate);

    }

    @Override
    public void claimAndComplateTask(String taskId, String userId, Map<String, Object> variableMap) {
        this.taskService.claim(taskId,userId);
        this.taskService.complete(taskId,variableMap);
    }

    @Override
    public void assignAssigneeForTask(String taskId, String assignee) {
        this.taskService.setAssignee(taskId,assignee);
    }

    @Override
    public void addCandidateUserToTask(String taskId, String candidate) {
        this.taskService.addCandidateUser(taskId,candidate);
    }

    @Override
    public void addCadidatesForTask(String taskId, List<String> candidates) {
        if(null != candidates && candidates.size() > 0){
            candidates.forEach( item -> {
                this.addCandidateUserToTask(taskId,item);
            });
        }
    }

    @Override
    public void removeCandidateForTask(String taskId, String candidate) {
        this.taskService.deleteCandidateUser(taskId,candidate);
    }

    @Override
    public void removeCandidatesForTask(String taskId, List<String> candidates) {
        if(null != candidates && candidates.size() > 0){
            candidates.forEach( item -> {
                this.removeCandidateForTask(taskId,item);
            });
        }
    }

    @Override
    public void replaceCandidateUserFromTask(String taskId, String oldCandidate, String newCandidate) {
        this.removeCandidateForTask(taskId,oldCandidate);
        this.addCandidateUserToTask(taskId,newCandidate);
    }

    @Override
    public void getAllElement(String procDefId) {

    }

    @Override
    public boolean isEndedByProcessInstanceId(String processInstanceId) {
        List<ProcessInstance> pis = this.runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).list();
        return (pis == null) || pis.isEmpty();
    }

    @Override
    public boolean isEndedByBussinessKey(String bussinessKey) {
        List<ProcessInstance> pis = this.runtimeService.createProcessInstanceQuery().processInstanceBusinessKey(bussinessKey).list();
        //System.out.println("pis===>"+JSON.toJSONString(pis));
        return (pis == null) || pis.isEmpty();
    }

    @Override
    public String queryBusinessKeyByTaskId(String taskId) {
        org.camunda.bpm.engine.task.Task task = this.taskService.createTaskQuery().taskId(taskId).singleResult();
        ProcessInstance pi = this.runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
        return pi.getBusinessKey();
    }

    @Override
    public ProcessInstanceTaskEntity transferTask(org.camunda.bpm.engine.task.Task task){
        if(null == task){
            return null;
        }
        ProcessInstanceTaskEntity taskEntity = new ProcessInstanceTaskEntity();

        taskEntity.setTaskId(task.getId());
        taskEntity.setTaskAssignee(task.getAssignee());
        taskEntity.setTaskDefKey(task.getTaskDefinitionKey());
        taskEntity.setTaskName(task.getName());
//        taskEntity.setTaskFormKey(task.getFormKey());
        taskEntity.setExecutionId(task.getExecutionId());
        taskEntity.setProcessInstanceId(task.getProcessInstanceId());
        taskEntity.setTaskOwner(task.getOwner());
        taskEntity.setTaskStartTime(task.getCreateTime());
        taskEntity.setTaskVar(this.taskService.getVariables(task.getId()));
        taskEntity.setCandidates(this.queryCandidateForTask(task.getId()));
        return taskEntity;
    }


    public void jump(String processInstanceId,String targetActivityId){
        // 整个流程的流程实例Id
//        String processInstanceId = "xxxxxx";
        // 需要跳转到的节点Id（可以是任意节点）
//        String targetActivityId = "Activity_0qs5wc1";

        ProcessInstanceModificationBuilder processInstanceModificationBuilder = runtimeService.createProcessInstanceModification(processInstanceId);
        Set<String> activityIdSet = new HashSet<>();
        taskService.createTaskQuery().processInstanceId(processInstanceId).active().list().forEach(taskQuery -> {
            String activityId = taskQuery.getTaskDefinitionKey();
            if(activityIdSet.add(activityId)){
                processInstanceModificationBuilder.cancelAllForActivity(activityId);
            }
        });

        processInstanceModificationBuilder.startBeforeActivity(targetActivityId)
                .setVariable("带入目标节点的变量key","变量值Object类型")
                .execute();

        System.out.println("流程处理成功");
    };







    // =========================== 一些查询视图  =====================================




//    ======= 流程轨迹查询 ，act_hi_actinst 为主表的查询
//    select
//    pi.ID_ as procInstId,
//    pi.BUSINESS_KEY_ as businessKey,
//    pi.PROC_DEF_KEY_ as procDefKey,
//    pi.START_TIME_ as startTime,
//    pi.END_TIME_ as endTime,
//    pi.STATE_ as procInstState,
//    act.ACT_ID_ as actId,
//    act.ACT_NAME_ as actName,
//    act.ACT_TYPE_ as actType
//    from act_hi_procinst pi
//    left join act_hi_actinst act on act.PROC_INST_ID_ = pi.ID_
//
// ======= 主流程信息查询，act_hi_procinst 为主表的查询
//    select
//    pi.ID_ as procInstId,
//    pi.BUSINESS_KEY_ as businessKey,
//    pi.PROC_DEF_KEY_ as procDefKey,
//    pi.START_TIME_ as startTime,
//    pi.END_TIME_ as endTime,
//    pi.STATE_ as procInstState,
//            (select max(START_TIME_) from act_hi_actinst act1 where act1.PROC_INST_ID_ = pi.ID_) as lastUpdateTime,
//(select group_concat(ACT_NAME_) from act_hi_actinst act2  where act2.PROC_INST_ID_ = pi.ID_) as currentSteps
//
//    from act_hi_procinst pi
//
//
//
//====== 待办列表的查询
//
//            select
//    *
//    from
//            (
//                    select
//                            distinct
//        INS.BUSINESS_KEY_ as BUSSINESSKEY,
//                    TASK.ID_ as TASKID,
//                    TASK.EXECUTION_ID_ as EXECUTIONID,
//                    TASK.PROC_INST_ID_ as PROCINSID,
//                    TASK.PROC_DEF_ID_ as PROCDEFID,
//                    TASK.NAME_ as TASKNAME,
//                    TASK.TASK_DEF_KEY_ as TASKDEFKEY,
//                    TASK.CREATE_TIME_ as TASKCREATETIME,
//        'assignee' as ASSIGNTYPE,
//                    TASK.ASSIGNEE_ as TASKUSERID
//                            from
//                            ACT_RU_TASK TASK
//                            left join ACT_RE_PROCDEF D on
//                            TASK.PROC_DEF_ID_ = D.ID_
//                            left join ACT_HI_PROCINST INS on
//                            TASK.PROC_INST_ID_ = INS.ID_
//                            where
//                            TASK.ASSIGNEE_ is not null
//                            and TASK.SUSPENSION_STATE_ = '1'
//                            union
//                            select
//                            distinct
//                            INS.BUSINESS_KEY_ as BUSSINESSKEY,
//                    TASK.ID_ as TASKID,
//                    TASK.EXECUTION_ID_ as EXECUTIONID,
//                    TASK.PROC_INST_ID_ as PROCINSID,
//                    TASK.PROC_DEF_ID_ as PROCDEFID,
//                    TASK.NAME_ as TASKNAME,
//                    TASK.TASK_DEF_KEY_ as TASKDEFKEY,
//                    TASK.CREATE_TIME_ as TASKCREATETIME,
//                    I.TYPE_ as ASSIGNTYPE,
//                    I.USER_ID_ as TASKUSERID
//                            from
//                            ACT_RU_TASK TASK
//                            left join ACT_RE_PROCDEF D on
//                            TASK.PROC_DEF_ID_ = D.ID_
//                            left join ACT_RU_VARIABLE V on
//                            V.PROC_INST_ID_ = TASK.PROC_INST_ID_
//                            left join ACT_RU_IDENTITYLINK I on
//                            I.TASK_ID_ = TASK.ID_
//                            left join ACT_HI_PROCINST INS on
//                            TASK.PROC_INST_ID_ = INS.ID_
//                            where
//                            TASK.ASSIGNEE_ is null
//                            and I.TYPE_ = 'candidate'
//                            and TASK.SUSPENSION_STATE_ = '1'
//            ) TODOLIST



}
